#include "hooks.h"
#include "rules.c"
#include "log.h"
#include "fw4.h"

#define REASON_NOT_IPV4 -10
struct nf_hook_ops handler_ops = {
	.list.next = NULL,
	.list.prev = NULL,
	.hook = packetHandler,	
	.pf = PF_INET, /* IPv4 */
	.hooknum = 0,
	.priority = NF_IP_PRI_FIRST
}; 

/*
 * 
 * 
 */

static void makeRawPacket(struct sk_buff *skb, rawPacket_t *packet, tcpHeaderParams_t *params){
	struct iphdr *iph; 
	struct tcphdr *tcph;
	struct icmphdr *icmph;
	struct udphdr *udph;	
	short sPort = 0, dPort = 0;	
	char protocol = 0;
	char *pointer;
	pointer = skb;
	iph = (struct iphdr *) skb_header_pointer(skb, 0, 0, NULL); // get IP header
	//Version
	packet->version = iph->version;
	pointer += sizeof(struct iphdr);
	//Protocol and IPs
	packet->protocol = iph->protocol;
	packet->source_ip = ntohl(iph->saddr);
	packet->dest_ip = ntohl(iph->daddr);
	//Ports
	if(iph->protocol == PROT_TCP){ //TCP
		tcph = (struct tcphdr*) (skb + sizeof(struct iphdr));
		params->syn = tcph->syn;
		params->ack = tcph->ack;
		params->fin = tcph->fin;
		params->rst = tcph->rst;
		sPort = ntohs(tcph->source);
		dPort = ntohs(tcph->dest);
		protocol = PROT_TCP;
		pointer += sizeof(struct tcphdr);	 
	}else if(iph->protocol == PROT_UDP){ //UDP
		udph = (struct udphdr*) (skb + sizeof(struct iphdr));
		sPort = ntohs(udph->source);
		dPort = ntohs(udph->dest);	
		protocol = PROT_UDP;
		pointer += sizeof(struct udphdr);	
	}else if(iph->protocol == PROT_ICMP){ //ICMP
		protocol = PROT_ICMP;
		pointer += sizeof(struct icmphdr);
	}
	packet->protocol = protocol;
	packet->source_port = sPort;
	packet->dest_port = dPort;
	packet->data = pointer;
	packet->data_length = (char*)(skb + skb->len) - pointer;
}
static unsigned int packetHandler(unsigned int hooknum,struct sk_buff *skb,const struct net_device *in,const struct net_device *out){
	rawPacket_t packet = { 0 };
	char ruleCheck = 0;
	int matchedIndex = -1;
	connection_t *conv = NULL;
	tcpHeaderParams_t tcpParams = { 0 };
	tcp_commands_t tcpCommand;
	tcp_states_t returnedState, currentState;
	if(skb == NULL){
		//TODO: what we do ?
		return NF_ACCEPT;
	}
	//Get information from sk_buff
	makeRawPacket(skb,&packet,&tcpParams);
	
	// check if firewall is enabled
	if(!(fw_config & FW_CONFIG_ACTIVE)){
		// Log the REASON_FW_INACTIVE for this packet.
		writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_ACCEPT, REASON_FW_INACTIVE, hooknum);
		return NF_ACCEPT;
	}
	
	if(packet.version != IP_VERSION){
		// Log the REASON_NOT_IPV4 for this packet.
		writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_NOT_IPV4, hooknum);
		return NF_DROP;
	}

	if(packet.protocol == PROT_TCP){	//TCP
		 if(!(fw_config & FW_CONFIG_TCP)){
			 // Log the REASON_PROT_NOT_ENFORCED for this packet.
			 writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_ACCEPT, REASON_PROT_NOT_ENFORCED, hooknum);
			 return NF_ACCEPT;
		 }else{
			 //Find previous conversation
			 conv = findConversation(&packet);
			 if(conv == CONV_NOT_FOUND){
				 tcpParams.side = CLIENT;
				 currentState = PRE_START;
			 }else{
				 currentState = conv->state;
				 if(conv->cli_ip == packet.source_ip){
					tcpParams.side = CLIENT;
				 }else{
					 tcpParams.side = SERVER;
				 }
			 }
			 tcpCommand = getCommand(tcpParams);
			 if(tcpCommand == BAD_COMMAND){
				writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_OUT_OF_STATE, hooknum);
				return NF_DROP;
			 }
			 returnedState = nextState(currentState,tcpCommand,tcpParams.side);
			 if(returnedState == BAD_STATE){
				writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_OUT_OF_STATE, hooknum);
				return NF_DROP;
			 }
			 //Set the new set
			 setState(conv,returnedState);
		 }
	}else if(packet.protocol == PROT_UDP){	//UDP
		if(!(fw_config & FW_CONFIG_UDP)){
			 // Log the REASON_PROT_NOT_ENFORCED for this packet.
			 writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_ACCEPT, REASON_PROT_NOT_ENFORCED, hooknum);
			 return NF_ACCEPT;
		 }
	}else if(packet.protocol == PROT_ICMP){	//ICMP
		if(!(fw_config & FW_CONFIG_ICMP)){
			 // Log the REASON_PROT_NOT_ENFORCED for this packet.
			 writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_ACCEPT, REASON_PROT_NOT_ENFORCED, hooknum);
			 return NF_ACCEPT;
		}	
	}
	// packet is ready for inspection
	ruleCheck = checkPermission(&packet,&matchedIndex);	
	
	if(ruleCheck == NF_ACCEPT) ruleCheck = 1;
	if(ruleCheck == NF_DROP) ruleCheck = 0;  
	if(ruleCheck == UNMATCHED_PACKET) ruleCheck = -1;
	printk("\n src:%pI4 %d dst:%pI4 %d prot:%d dataLen:%d ruleCheck:%d\n",&packet.source_ip,packet.source_port,&packet.dest_ip,packet.dest_port,packet.protocol,packet.data_length,ruleCheck);
	printk("\ns %u, d %u\n",packet.source_ip,packet.dest_ip);
	
	if(ruleCheck == UNMATCHED_PACKET){
		if(fw_config & FW_CONFIG_CLEANUP_ACCEPT){
			 writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_ACCEPT, REASON_NO_MATCHING_RULE, hooknum);
			return NF_ACCEPT;
		}else{
			writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_NO_MATCHING_RULE, hooknum);
			return NF_DROP;			
		}
	}
	writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, ruleCheck, matchedIndex, hooknum);
	return ruleCheck;
}
